using System;
using System.Collections.Generic;
using System.Text;
using org.ngwt.user.client.rpc;
using org.ngwt.user.client.rpc.impl;
using org.ngwt.user.server.rpc;
using System.Reflection;


namespace org.ngwt.user.server.rpc.impl
{
    #region ValueWriterr
    abstract class ValueWriter
    {
        public abstract void write(ServerSerializationStreamWriter stream, Object instance);
    }

    class BooleanWriter : ValueWriter {
        public override void write(ServerSerializationStreamWriter stream, Object instance){
            stream.writeBoolean(((bool)instance));
        }
    }

    class ByteWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeByte(((byte)instance));
        }
    }

    class CharWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeChar(((char)instance));
        }
    }

    class DoubleWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeDouble(((double)instance));
        }
    }

    class FloatWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeFloat(((float)instance));
        }
    }

    class IntWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeInt(((int)instance));
        }
    }
    class LongWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeLong(((long)instance));
        }
    }
    class ObjectWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeObject(instance);
        }
        public void write(ServerSerializationStreamWriter stream, Object instance,Type type)
        {
            stream.writeObject(instance,type);
        }
    }
    class ShortWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeShort(((short)instance));
        }
    }
    class StringWriter : ValueWriter
    {
        public override void write(ServerSerializationStreamWriter stream, Object instance)
        {
            stream.writeString(((string)instance));
        }
    }
    #endregion

    #region VectorWriter
    abstract class VectorWriter
    {
        public abstract void write(ServerSerializationStreamWriter stream, Object instance);
    };

    class BooleanVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            bool[] vector = (bool[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeBoolean(vector[i]);
            }            
        }
    };

    class ByteVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            byte[] vector = (byte[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeByte(vector[i]);
            }
        }
    };

    class CharVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            char[] vector = (char[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeChar(vector[i]);
            }
        }
    };

    class DoubleVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            double[] vector = (double[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeDouble(vector[i]);
            }
        }
    };

    class FloatVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            float[] vector = (float[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeFloat(vector[i]);
            }
        }
    };

    class IntVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            int[] vector = (int[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeInt(vector[i]);
            }
        }
    };

    class LongVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            long[] vector = (long[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeLong(vector[i]);
            }
        }
    };

    class ObjectVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            object[] vector = (object[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeObject(vector[i]);
            }
        }
    };

    class ShortVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            short[] vector = (short[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeShort(vector[i]);
            }
        }
    };

 /*   class StringVectorWriter : VectorWriter
    {
        public override void write(ServerSerializationStreamWriter stream, object instance)
        {
            string[] vector = (string[])instance;
            stream.writeInt(vector.Length);
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                stream.writeString(vector[i]);
            }
        }
    };
  * */

    #endregion


    public class ServerSerializationStreamWriter : AbstractSerializationStreamWriter
    {

        private static char NON_BREAKING_HYPHEN = '\u2011';

         /**
          * Number of escaped JS Chars.
          */
         private static int NUMBER_OF_JS_ESCAPED_CHARS = 128;

         /**
          * A list of any characters that need escaping when printing a JavaScript
          * string literal. Contains a 0 if the character does not need escaping,
          * otherwise contains the character to escape with.
          */
         private static char[] JS_CHARS_ESCAPED = new char[NUMBER_OF_JS_ESCAPED_CHARS];

         /**
          * This defines the character used by JavaScript to mark the start of an
          * escape sequence.
          */
         private static char JS_ESCAPE_CHAR = '\\';

         /**
          * This defines the character used to enclose JavaScript strings.
          */
         private static char JS_QUOTE_CHAR = '\"';

         /**
          * Index into this array using a nibble, 4 bits, to get the corresponding
          * hexa-decimal character representation.
          */
         private static char[] NIBBLE_TO_HEX_CHAR = {
              '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
              'E', 'F'};

        /**
         * Map of {@link Class} objects to {@link ValueWriterr}s.
         */
        private static Dictionary<Type, ValueWriter> CLASS_TO_VALUE_WRITER = new Dictionary<Type, ValueWriter>();

        /**
         * Map of {@link Class} vector objects to {@link VectorWriter}s.
         */
        private static Dictionary<Type, VectorWriter> CLASS_TO_VECTOR_WRITER = new Dictionary<Type, VectorWriter>();

        private static ObjectWriter objWriter = new ObjectWriter();
        private static ObjectVectorWriter objVectorWriter = new ObjectVectorWriter();

        static ServerSerializationStreamWriter()
        {
            /*
             * NOTE: The JS VM in IE6 & IE7 do not interpret \v correctly. They convert
             * JavaScript Vertical Tab character '\v' into 'v'. As such, we do not use
             * the short form of the unicode escape here.
             */
            JS_CHARS_ESCAPED['\u0000'] = '0';
            JS_CHARS_ESCAPED['\b'] = 'b';
            JS_CHARS_ESCAPED['\t'] = 't';
            JS_CHARS_ESCAPED['\n'] = 'n';
            JS_CHARS_ESCAPED['\f'] = 'f';
            JS_CHARS_ESCAPED['\r'] = 'r';
            JS_CHARS_ESCAPED[JS_ESCAPE_CHAR] = JS_ESCAPE_CHAR;
            JS_CHARS_ESCAPED[JS_QUOTE_CHAR] = JS_QUOTE_CHAR;

            CLASS_TO_VECTOR_WRITER.Add(typeof(bool[]), new BooleanVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(byte[]), new ByteVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(char[]), new CharVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(double[]), new DoubleVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(float[]), new FloatVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(int[]), new IntVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(long[]), new LongVectorWriter());
            CLASS_TO_VECTOR_WRITER.Add(typeof(Object[]), objVectorWriter);
            CLASS_TO_VECTOR_WRITER.Add(typeof(short[]), new ShortVectorWriter());
            //CLASS_TO_VECTOR_WRITER.Add(typeof(String[]), new StringVectorWriter());

            CLASS_TO_VALUE_WRITER.Add(typeof(bool), new BooleanWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(byte), new ByteWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(char), new CharWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(double), new DoubleWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(float), new FloatWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(int), new IntWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(long), new LongWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(Object), objWriter);
            CLASS_TO_VALUE_WRITER.Add(typeof(short), new ShortWriter());
            CLASS_TO_VALUE_WRITER.Add(typeof(String), new StringWriter());
        }

        /**
         * This method takes a string and outputs a JavaScript string literal. The
         * data is surrounded with quotes, and any contained characters that need to
         * be escaped are mapped onto their escape sequence.
         * 
         * Assumptions: We are targeting a version of JavaScript that that is later
         * than 1.3 that supports unicode strings.
         */
        private static String escapeString(String toEscape)
        {
            // make output big enough to escape every character (plus the quotes)
            char[] input = toEscape.ToCharArray();
            
            CharVector charVector = new CharVector(input.Length * 2 + 2, input.Length);

            charVector.add(JS_QUOTE_CHAR);

            for (int i = 0, n = input.Length; i < n; ++i)
            {
                char c = input[i];
                if (c < NUMBER_OF_JS_ESCAPED_CHARS && JS_CHARS_ESCAPED[c] != 0)
                {
                    charVector.add(JS_ESCAPE_CHAR);
                    charVector.add(JS_CHARS_ESCAPED[c]);
                }
                else if (needsUnicodeEscape(c))
                {
                    charVector.add(JS_ESCAPE_CHAR);
                    unicodeEscape(c, charVector);
                }
                else
                {
                    charVector.add(c);
                }
            }

            charVector.add(JS_QUOTE_CHAR);
            return new String(charVector.asArray(), 0, charVector.getSize());
        }

        /**
         * Returns <code>true</code> if the character requires the \\uXXXX unicode
         * character escape sequence. This is necessary if the raw character could be
         * consumed and/or interpreted as a special character when the JSON encoded
         * response is evaluated. For example, 0x2028 and 0x2029 are alternate line
         * endings for JS per ECMA-232, which are respected by Firefox and Mozilla.
         * 
         * @param ch character to check
         * @return <code>true</code> if the character requires the \\uXXXX unicode
         *         character escape
         * 
         * Notes:
         * <ol>
         * <li> The following cases are a more conservative set of cases which are are
         * in the future proofing space as opposed to the required minimal set. We
         * could remove these and still pass our tests.
         * <ul>
         * <li>UNASSIGNED - 6359</li>
         * <li>NON_SPACING_MARK - 530</li>
         * <li>ENCLOSING_MARK - 10</li>
         * <li>COMBINING_SPACE_MARK - 131</li>
         * <li>SPACE_SEPARATOR - 19</li>
         * <li>CONTROL - 65</li>
         * <li>PRIVATE_USE - 6400</li>
         * <li>DASH_PUNCTUATION - 1</li>
         * <li>Total Characters Escaped: 13515</li>
         * </ul>
         * </li>
         * <li> The following cases are the minimal amount of escaping required to
         * prevent test failure.
         * <ul>
         * <li>LINE_SEPARATOR - 1</li>
         * <li>PARAGRAPH_SEPARATOR - 1</li>
         * <li>FORMAT - 32</li>
         * <li>SURROGATE - 2048</li>
         * <li>Total Characters Escaped: 2082</li>
         * </li>
         * </ul>
         * </li>
         * </ol>
         */
        private static bool needsUnicodeEscape(char ch)
        {
            switch (java.lang.Character.getType(ch))
            {
                // Conservative
                case java.lang.Character.COMBINING_SPACING_MARK:
                case java.lang.Character.ENCLOSING_MARK:
                case java.lang.Character.NON_SPACING_MARK:
                case java.lang.Character.UNASSIGNED:
                case java.lang.Character.PRIVATE_USE:
                case java.lang.Character.SPACE_SEPARATOR:
                case java.lang.Character.CONTROL:

                // Minimal
                case java.lang.Character.LINE_SEPARATOR:
                case java.lang.Character.FORMAT:
                case java.lang.Character.PARAGRAPH_SEPARATOR:
                case java.lang.Character.SURROGATE:
                    return true;

                default:
                    if (ch == NON_BREAKING_HYPHEN)
                    {
                        // This can be expanded into a break followed by a hyphen
                        return true;
                    }
                    break;
            }

            return false;
        }

        /**
         * Writes either the two or four character escape sequence for a character.
         * 
         * 
         * @param ch character to unicode escape
         * @param charVector char vector to receive the unicode escaped representation
         */
        private static void unicodeEscape(char ch, CharVector charVector)
        {
            if (ch < 256)
            {
                charVector.add('x');
                charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
                charVector.add(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
            }
            else
            {
                charVector.add('u');
                charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 12) & 0x0F]);
                charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 8) & 0x0F]);
                charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
                charVector.add(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
            }
        }

        private int objectCount;

        private Dictionary<Object, int> objectMap = new Dictionary<Object, int>();

        private Dictionary<String, int> stringMap = new Dictionary<String, int>();

        private List<String> stringTable = new List<String>();

        private List<String> tokenList = new List<String>();

        private int tokenListCharCount;

        private SerializationPolicy serializationPolicy;


        public ServerSerializationStreamWriter(SerializationPolicy serializationPolicy) {
          this.serializationPolicy = serializationPolicy;
        }

        public void prepareToWrite()
        {
            objectCount = 0;
            objectMap.Clear();
            tokenList.Clear();
            tokenListCharCount = 0;
            stringMap.Clear();
            stringTable.Clear();
        }

        public void serializeValue(Object value, Type type)
        {
          ValueWriter valueWriter = CLASS_TO_VALUE_WRITER.ContainsKey(type) ? CLASS_TO_VALUE_WRITER[type] :null;
          if (valueWriter != null) {
            valueWriter.write(this, value);
          } else {
            // Arrays of primitive or reference types need to go through writeObject.
            objWriter.write(this, value,type);
          }
        }

        public override string toString()
        {
            // Build a JavaScript string (with escaping, of course).
            // We take a guess at how big to make to buffer to avoid numerous resizes.
            //
            int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.Count;
            StringBuilder buffer = new StringBuilder(capacityGuess);
            buffer.Append("[");
            writePayload(buffer);
            writeStringTable(buffer);
            writeHeader(buffer);
            buffer.Append("]");
            return buffer.ToString();
        }

        protected override int addString(string str)
        {
            if (str == null) {
              return 0;
            }
            if (stringMap.ContainsKey(str))
            {
                return stringMap[str];
            }
            stringTable.Add(str);
            // index is 1-based
            int index = stringTable.Count;
            stringMap.Add(str, index);
            return index;
        }

        protected override void append(string token)
        {
            tokenList.Add(token);
            if (token != null)
            {
                tokenListCharCount += token.Length;
            }
        }

        protected override int getIndexForObject(object instance)
        {
            if (objectMap.ContainsKey(instance))
            {
                return objectMap[instance];
            }
            return -1;
        }

        protected override string getObjectTypeSignature(object instance)
        {
            if (shouldEnforceTypeVersioning())
            {
                return SerializabilityUtil.encodeSerializedInstanceReference(instance.GetType());
            }
            else
            {
                return SerializabilityUtil.getSerializedTypeName(instance.GetType());
            }
        }
        protected override string getTypeSignature(Type clazz)
        {
            if (shouldEnforceTypeVersioning())
            {
                return SerializabilityUtil.encodeSerializedInstanceReference(clazz);
            }
            else
            {
                return SerializabilityUtil.getSerializedTypeName(clazz);
            }
        }

        protected override void saveIndexForObject(object instance)
        {
            objectMap.Add(instance, objectCount++);
        }

        protected override void serialize(object instance, string typeSignature, Type clazz)
        {
            if(clazz ==null)
                clazz = instance.GetType();
            serializationPolicy.validateSerialize(clazz);
            serializeImpl(instance, clazz);
        }
        protected override void serialize(object instance, string typeSignature)
        {
            //assert (instance != null);
            serialize(instance, typeSignature, null);
        }

        /**
         * Serialize an instance that is an array. Will default to serializing the
         * instance as an Object vector if the instance is not a vector of primitives,
         * Strings or Object.
         * 
         * @param instanceClass
         * @param instance
         * @throws SerializationException
         */
        private void serializeArray(Type instanceClass, Object instance)
        {
          //assert (instanceClass.isArray());

            VectorWriter instanceWriter = CLASS_TO_VECTOR_WRITER.ContainsKey(instanceClass) 
                ? CLASS_TO_VECTOR_WRITER[instanceClass] : null;
          if (instanceWriter != null) {
            instanceWriter.write(this, instance);
          } else {
              objVectorWriter.write(this, instance);
          }
        }
        
        private void serializeImpl(Object instance, Type instanceClass)
        {
          //assert (instance != null);

          Type customSerializer = SerializabilityUtil.hasCustomFieldSerializer(instanceClass);
          if (customSerializer != null) {
            // Use custom field serializer
            serializeWithCustomSerializer(customSerializer, instance, instanceClass);
          } else if (instanceClass.IsArray) {
            serializeArray(instanceClass, instance);
          } else {
            // Regular class instance
            serializeClass(instance, instanceClass);
          }
        }

        private void serializeClass(Object instance, Type instanceClass)
        {
          //assert (instance != null);

          PropertyInfo[] serializableProperties = SerializabilityUtil.applyFieldSerializationPolicy(instanceClass);
          foreach (PropertyInfo declProperty in serializableProperties)
          {
            //assert (declField != null);

            //bool isAccessible = declField.isAccessible();
            //bool needsAccessOverride = !isAccessible
            //    && !Modifier.isPublic(declField.getModifiers());
            //if (needsAccessOverride) {
            //  // Override the access restrictions
            //  declField.setAccessible(true);
            //}

            Object value;
            try {
                value = declProperty.GetValue(instance,null);
              serializeValue(value, declProperty.PropertyType);

          }
          catch (ArgumentException e)
          {
              throw new SerializationException(e);

          }
          catch (FieldAccessException e)
          {
              throw new SerializationException(e);
            }

            //if (needsAccessOverride) {
            //  // Restore the access restrictions
            //  declField.setAccessible(isAccessible);
            //}
          }

          Type superClass = instanceClass.BaseType;
          if (serializationPolicy.shouldSerializeFields(superClass)) {
            serializeImpl(instance, superClass);
          }
        }  
      
        private void serializeWithCustomSerializer(Type customSerializer,Object instance, Type instanceClass) 
        {
          MethodInfo serialize;
          try
          {
              //assert (!instanceClass.isArray());

              Type[] typeArray = new Type[2];
              typeArray[0] = typeof(SerializationStreamReader);
              typeArray[1] = instanceClass;
              serialize = customSerializer.GetMethod("serialize",BindingFlags.Public | BindingFlags.Static);
              //serialize = customSerializer.GetMethod("serialize", BindingFlags.Public | BindingFlags.Static, null, typeArray, null);

              object[] paramArray = new object[2];
              paramArray[0] = this;
              paramArray[1] = instance;
              serialize.Invoke(null, paramArray);

          }
          catch (Exception e)
          {
              throw new SerializationException(e.Message);
          }

        }





        /**
         * Notice that the field are written in reverse order that the client can just
         * pop items out of the stream.
         */
        private void writeHeader(StringBuilder buffer)
        {
            buffer.Append(",");
            buffer.Append(getFlags());
            buffer.Append(",");
            buffer.Append(getVersion());
        }

        private void writePayload(StringBuilder buffer)
        {
            for (int i = tokenList.Count - 1; i >= 0; --i)
            {
                String token = tokenList[i];
                buffer.Append(token);
                if (i > 0)
                {
                    buffer.Append(",");
                }
            }
        }

        private void writeStringTable(StringBuilder buffer)
        {
            if (tokenList.Count > 0)
            {
                buffer.Append(",");
            }
            buffer.Append("[");
            for (int i = 0, c = stringTable.Count; i < c; ++i)
            {
                if (i > 0)
                {
                    buffer.Append(",");
                }
                buffer.Append(escapeString(stringTable[i]));
            }
            buffer.Append("]");
        }
    }
}
